/*
 *
 *  * Copyright 2015-2101 Shouqi Limousine & chauffeur Group.
 *  *
 *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  * you may not use this file except in compliance with the License.
 *  * You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  * Unless required by applicable law or agreed to in writing, software
 *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  * See the License for the specific language governing permissions and
 *  * limitations under the License.
 *
 */

package com.sq.plugins.redis.starter;

import com.sq.plugin.redis.client.RedisClusterClient;
import com.sq.plugin.redis.client.RedisSentinelClient;
import com.sq.plugin.redis.enums.SerializerEnum;
import com.sq.plugins.redis.cluster.RedisClusterConfig;
import com.sq.plugins.redis.config.RedisConfig;
import com.sq.plugins.redis.config.RedisConfigFactory;
import com.sq.plugins.redis.exception.RedisAutoConfigException;
import com.sq.plugins.redis.sentinel.RedisSentinelConfig;
import com.sq.plugins.redis.sentinel.RedisSentinelPoolConfig;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;

import java.util.ArrayList;
import java.util.List;

/**
 * @author wangliang
 */
public class RedisAutoCreator implements BeanFactoryPostProcessor {

    private RedisConfig redisConfig;

    private static final String PREFIX = "spring.redis";

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
        redisConfig = RedisConfigFactory.getRedisConfig(beanFactory, PREFIX);
        if (redisConfig == null) {
            throw new RedisAutoConfigException("config is not exist");
        }
        registryClientBean(beanFactory);
    }

    private void registryClientBean(DefaultListableBeanFactory beanFactory) {
        List<RedisClusterConfig> clusters = redisConfig.getClusters();
        List<RedisSentinelConfig> sentinels = redisConfig.getSentinels();
        List<RedisSentinelPoolConfig> sentinelPools = redisConfig.getSentinelPools();

        if (clusters == null || sentinels == null) {
            throw new RedisAutoConfigException("clusters or sentinels not exist");
        }
        if (!clusters.isEmpty()){
            for (RedisClusterConfig redisClusterConfig : clusters) {
                regisgerPoolConfig(beanFactory, redisClusterConfig);
            }
        }
        if (!sentinelPools.isEmpty()){
            for (RedisSentinelPoolConfig pool : sentinelPools) {
                regisgerPoolConfig(beanFactory, pool);
            }
        }

        if (!sentinels.isEmpty()){
            for (RedisSentinelConfig sentinelConfig : sentinels) {
                regisgerSentinelConfig(beanFactory, sentinelConfig);
            }
        }

    }


    /**
     * 注册bean
     *
     * @param beanFactory
     * @param redisClusterConfig
     */
    private void regisgerPoolConfig(DefaultListableBeanFactory beanFactory, RedisClusterConfig redisClusterConfig) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisClusterClient.class);
        builder.addConstructorArgValue(redisClusterConfig.getHostAndPort())
                .addConstructorArgValue(redisClusterConfig.getPrefix())
                .addConstructorArgValue(redisClusterConfig.getAuth())
                .addConstructorArgValue(SerializerEnum.JACKSON2JSON)
                .addConstructorArgValue(redisClusterConfig.getMaxIdle())
                .addConstructorArgValue(redisClusterConfig.getMaxTotal())
                .addConstructorArgValue(redisClusterConfig.getMinIdle())
                .addConstructorArgValue(redisClusterConfig.getConnectionTimeout())
                .addConstructorArgValue(redisClusterConfig.getSoTimeout())
                .addConstructorArgValue(redisClusterConfig.getMaxAttempts());
        beanFactory.registerBeanDefinition(redisClusterConfig.getName(), builder.getBeanDefinition());
    }

    /**
     * 注册poolBean
     *
     * @param beanFactory
     * @param config
     */
    private void regisgerPoolConfig(DefaultListableBeanFactory beanFactory, RedisSentinelPoolConfig config) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(JedisSentinelPool.class);

        GenericObjectPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(config.getMaxIdle());
        poolConfig.setMaxTotal(config.getMaxTotal());
        poolConfig.setMinIdle(config.getMinIdle());

        builder.addConstructorArgValue(config.getMaster())
                .addConstructorArgValue(config.getHostAndPorts())
                .addConstructorArgValue(poolConfig)
                .addConstructorArgValue(config.getConnectionTimeout())
                .addConstructorArgValue(config.getSoTimeout())
                .addConstructorArgValue(config.getPassword())
                .addConstructorArgValue(0);
        beanFactory.registerBeanDefinition(config.getPoolName(), builder.getBeanDefinition());
    }


    /**
     * 注册SentinelBean
     * @param beanFactory
     * @param config
     */
    private void regisgerSentinelConfig(DefaultListableBeanFactory beanFactory, RedisSentinelConfig config) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisSentinelClient.class);

        builder.addConstructorArgReference(config.getDefaultPoolRef());
        List<JedisSentinelPool> list = new ArrayList<>();
        for (String poolName : config.getPoolRefs()) {
            list.add((JedisSentinelPool) beanFactory.getBean(poolName));
        }
        builder.addConstructorArgValue(list);
        beanFactory.registerBeanDefinition(config.getName(), builder.getBeanDefinition());

    }
}
